<?php
require_once 'class/item/Item.php';
require_once 'class/meta/Ticker.php';
require_once 'class/space/Room.php';

define('AMUD_SLOT_LEFT_RING','left_ring');
define('AMUD_SLOT_RIGHT_RING','right_ring');
define('AMUD_SLOT_LEFT_HAND','left_hand');
define('AMUD_SLOT_RIGHT_HAND','right_hand');
define('AMUD_SLOT_NECK','neck');
define('AMUD_SLOT_HEAD','head');
define('AMUD_SLOT_FACE','face');
define('AMUD_SLOT_LEFT_EAR','left_ear');
define('AMUD_SLOT_RIGHT_EAR','right_ear');
define('AMUD_SLOT_FEET','feet');
define('AMUD_SLOT_LEGS','legs');
define('AMUD_SLOT_CHEST','chest');
define('AMUD_SLOT_HANDS','hands');
define('AMUD_SLOT_ARMS','arms');
define('AMUD_SLOT_BACK','back');
define('AMUD_SLOT_WAIST','waist');
define('AMUD_SLOT_WRIST','wrist');
define('AMUD_STATUS_ALL','all');

/**
 * This constant will limit the iterations for the status calculation
 *no damage can still more than 100 ticks and in 100 ticks a character will regen 100%
 *so 200 ticks may be enough
 *
 **/
define('AMUD_MAX_TICKS_FOR_STATUS_CALC',200);
/**
 * Char class, holds characters data retrieve db info and save it too.
 * @package char
 * @author alcaitiff
 */
class Char extends ExportableObject
{
	protected 	$charId,
				$HP,$MP,$XP,
				$room,$items,
				$status,$statusCapabilities,$capabilities,$statusCommands,
				$baseStrength,$baseDexterity,$baseVitality,$baseIntelligence,
				$roles;
	
	private		$level,$tick,$tickSize,$tickNum,$updated,$dead,$cachedRoom=null;
	
	/**
     * @static
     * @var array
     */
    static protected $slots=array(	AMUD_SLOT_LEFT_RING,		AMUD_SLOT_RIGHT_RING,
									AMUD_SLOT_LEFT_HAND,		AMUD_SLOT_RIGHT_HAND,
									AMUD_SLOT_LEFT_EAR,			AMUD_SLOT_RIGHT_EAR,
									AMUD_SLOT_NECK,				AMUD_SLOT_HEAD,
									AMUD_SLOT_FACE,				AMUD_SLOT_FEET,
									AMUD_SLOT_LEGS,				AMUD_SLOT_CHEST,
									AMUD_SLOT_HANDS,			AMUD_SLOT_ARMS,
									AMUD_SLOT_BACK,				AMUD_SLOT_WAIST);
	
	/**
     * Constructor
     * @param string $id Character name
     * @return Char
     */
	function __construct ($id=null){
		$this->charId=$id;
		$this->items=array();
		$this->status=array();
		$this->roles=array();
		$this->statusCapabilities=array();
		$this->capabilities=array();
		$this->level=0;
		$this->dead=false;
		foreach(self::$slots as $slot){
			$this->items[$slot]=null;
		}
	}
    /**
     * Receive a constant slot and return the item or null
     * @param string $slot
     * @return Item
     */
	public function getItemsBySlot($slot){
		if(in_array($slot,self::$slots)){
			if(!isset($this->items[$slot]) || $this->items[$slot]==null){
				$this->retrieveCurrValues();
			}
			return $this->items[$slot];
		}else{
			return null;
		}
	}
    /**
     * Verify if character is holding an item named $value
     * @param string $value Item name
     * @return bool
     */
    public function holdItemName($value){
        if($this->items[AMUD_SLOT_LEFT_HAND]->getType()==$value || $this->items[AMUD_SLOT_RIGHT_HAND]->getType()==$value ){
            return true;
        }else{
            return false;
        }
    }
    /**
     * Return the hand slot that is free for the type
     * @param string $itemType
     * @return string
     */
    public function getFreeHandSlot($itemType=null){
        if( $this->items[AMUD_SLOT_RIGHT_HAND]->getId()===NULL ||
           ($this->items[AMUD_SLOT_RIGHT_HAND]->getType()==$itemType && $this->items[AMUD_SLOT_RIGHT_HAND]->isStackable())
           ){
            return AMUD_SLOT_RIGHT_HAND;
        }elseif( $this->items[AMUD_SLOT_LEFT_HAND]->getId()===NULL ||
                ($this->items[AMUD_SLOT_LEFT_HAND]->getType()==$itemType && $this->items[AMUD_SLOT_LEFT_HAND]->isStackable())
                ){
            return AMUD_SLOT_LEFT_HAND;
        }else{
            return null;
        }
    }
    /**
     * Drop an item from hands to floor
     * @param int $num
     * @param string $name
     * @param int $index
     * @return string 
     */
    public function dropItemFromHands($num,$name,$index){
        if($index>2){
            MessageSystem::addErrorMessage('You have only 2 hands');
            return null;
        }
        $offset=0;
        if($index==1){
            if($this->isItemNameOnSlot($name, AMUD_SLOT_RIGHT_HAND)){
                return $this->drop($num,AMUD_SLOT_RIGHT_HAND);
            }else{
                $offset=1;
            }
        }
        if($index==2 || $offset==1){
            //must be on left hand
            if($this->isItemNameOnSlot($name, AMUD_SLOT_LEFT_HAND)){
                return $this->drop($num,AMUD_SLOT_LEFT_HAND);
            }
        }
        if($index>1){
            $errorMessage=$name.'['.$index.'] not found';
        }else{
            $errorMessage=$name.' not found';
        }
        MessageSystem::addErrorMessage($errorMessage);
        return null;
    }
    /**
     * Drop a num of items on floor
     * @param int $num
     * @param string $slot
     * @return string
     */
    public function drop($num,$slot){
        $item = $this->getItemsBySlot($slot);
        if(!$item->stackHaveThatMuch($num)){
            return null;
        }
        $newItem = $item->split($num);
        if ($newItem === null) {
            $this->removeItemBySlot($slot);
            $this->getRoom()->addItem($item);
            $n='('.$item->stack.')';
        } else {
            $this->getRoom()->addItem($newItem);
            $n='('.$newItem->stack.')';
        }
        $this->getRoom()->broadcastMessage($this->charId.' dropped '.$item->getType().$n, $this);
        return 'You dropped ' . $item->getType() . $n.' on floor';
    }

    /**
     * Drop an item from a container
     * @param Item $item
     * @param int $num
     * @param Item $container
     * @return string
     */
    public function dropItemFromContainer($item,$num,$container){
        if(!$item->stackHaveThatMuch($num)){
            return null;
        }
        $newItem = $item->split($num);
        if ($newItem === null) {
            if($container->container->removeItem($item)){
                $this->getRoom()->addItem($item);
                $n='('.$item->stack.')';
            }else{
                MessageSystem::addErrorMessage('Cannot remove item from container');
                return false;
            }
        } else {
            $this->getRoom()->addItem($newItem);
            $n='('.$newItem->stack.')';
        }
        $this->getRoom()->broadcastMessage($this->charId.' dropped '.$item->getType().$n, $this);
        return 'You dropped ' . $item->getType().$n . ' on floor';
    }
    /**
     * Refresh the char values and status to the $tick timestamp
     * @param int $tick
     */
	public function refresh($tick){
		if(!isset($this->updated)){
			//get roles and level
			$this->retrieveRoles();
			//get the items, attributes and current values
			$this->retrieveCurrValues();
			$valuesGot=true;
		}else{
			$valuesGot=false;
		}

        //remove the outdated status
		//it keep only the old stats that are related to life/mana regeneration/damage
		$this->removeUselessStatus();
		//get the current player status
		$this->retrieveCurrStatus();

        if($this->updated<$tick){
			MessageSystem::addDebugMessage('Refreshing '.$this->charId.' on '.$tick);
			if(!$valuesGot){
				//get roles and level
				$this->retrieveRoles();
				//get the items, attributes and current values
				$this->retrieveCurrValues();
			}
			//Setting the tick properties for status update
			$this->tick=$tick;
			$this->tickSize=Ticker::getTickSize();
			$this->tickNum=floor(($tick-$this->updated)/$this->tickSize);
			$this->updated=$tick-($this->tickNum*$this->tickSize);
			if($this->tickNum>AMUD_MAX_TICKS_FOR_STATUS_CALC){
				$this->tickNum=AMUD_MAX_TICKS_FOR_STATUS_CALC;
			}
			//verify if the char is dead
			if(!$this->isDead()){
				//remove outdated status, update dates and calc regen/damage
				if($this->tickNum>0){
					$this->updateValues($this->tickNum);
				}
			}else{
				$this->removeOutdatedStatus();
			}
			//apply the status values
			$this->applyStatus();
			//calculate the character capabilities
			$this->calcCapabilities();
			//update char values
			$this->updated=$this->tick;
			$this->saveValues();
		}
	}
    /**
     * Retrieve the char room
     * @return Room
     */
	public function getRoom(){
        if($this->cachedRoom===null){
            $this->retrieveRoom();
            $this->cachedRoom=new Room($this->room);
        }
        return $this->cachedRoom;
	}
	/**
     * This function set a new room to char, if you set the old room this function no instance a new object
     *
     * May rise an exception if the room is invalid
     * @param Room $room
     * @param string $direction
     * @param Room $old the old room
     * @return Room
     */
	public function setRoom($room,$direction,$old=null){
		if($room instanceof Room){

			if(!($old instanceof Room) || $old->getId()!=$this->room){
				$old=$this->getRoom();
			}

			$old->leave($this,$direction);
			
			$this->room=$room->getId();
			
			$this->saveRoom();

			$room->arrive($this,Room::reverseDir($direction));
			
			return $room;
		}else{
			throw new Exception('Trying to set an invalid room');
		}
	}
    /**
     * Verify if player is dead
     * @return bool
     */
	public function isDead(){
		if(!$this->dead && $this->HP<=0){
			$this->setStatus('dead',$this->updated,1);
			$this->dead=true;
		}
		return $this->dead;
	}
    /**
     * Add a status to the char
     * @param string $status
     * @param timestamp $time
     * @param int $level
     */
    public function addStatus($status,$time,$level){
        $this->setStatus($status, $time, $level);
        $s = new Status($status, $level, $time);
        $room=$this->getRoom();
        $room->broadcastMessage($this->charId.' got '.$s->description, $this);
        MessageSystem::addCharacterMessage('You got '.$s->description);
    }
    /**
     *
     * @param string $status 
     */
    public function disableStatus($status){
        $this->removeStatus($status);
    }
    /**
     * Verify if a status disabled the desired command
     * @param string $action the desired command
     * @return bool
     */
    public function canDo($action){
        //Status all only block and dont stack
        if(isset($this->statusCommands[AMUD_STATUS_ALL])){
            $mod=-999;
        }else{
            $mod=0;
        }
        //When All is set you must a 1000 to allow a command
        //a bug may occour if a status stack over 1000 times, but huge status stacks are not desired yet
        if(!isset($this->statusCommands[$action])){
            $mod+=1;
        }else{
            $mod+=$this->statusCommands[$action];
        }
        return ($mod>0);
    }
    /**
     * Set refresh true on character
     * @return void
     */
	public function hasChanged(){
		$this->changeRefresh(1);
	}
    /**
     * Get a visible char
     * @param string $name
     * @param int $index
     * @param string $errorMessage
     * @return Char 
     */
    public function getChar($name,$index,$errorMessage=null){
        //Get the char room
		$room=$this->getRoom();

		//1º the element may be a character
		//Get the characters on the room
		$chars=$room->getCharactersByName($name,$index);
		$offset=count($chars);
		if($offset>=$index){
			//found!
			$chars=array_slice($chars,$index-1,1);
			$el = new Char($chars[0]);
			$el->refresh($this->updated);
			return $el;
		}else{
            if($errorMessage!==null){
                MessageSystem::addErrorMessage($errorMessage);
                return null;
            }else{
                if($index>1){
                    $err=$name.'['.$index.'] not found';
                }else{
                    $err=$name.' not found';
                }
                MessageSystem::addErrorMessage($err);
                return null;
            }
        }
    }
    /**
     * This function receive an element name and index and return an element visible to the character
     * This element may be another character, a room, a door, an item on his hands or an item on the floor
     * @param string $name Element name
     * @param int $index Element number
     * @return Char|Room|String|Item|Array
     */
	public function getElement($name,$index){
		//Get the char room
		$room=$this->getRoom();

		//1º the element may be a character
		//Get the characters on the room
		$chars=$room->getCharactersByName($name,$index);
		$offset=count($chars);
		if($offset>=$index){
			//found!
			$chars=array_slice($chars,$index-1,1);
			$el = new Char($chars[0]);
			$el->refresh($this->updated);
			return $el;
		}

		//2º the element may be a direction
		$dir=$room->getDirectionByName($name);
		if($dir!=null){
			$offset++;
		}
		if($index-$offset==0){
			//found!
			return $dir;
		}
		if($index>1){
			$err=$name.'['.$index.'] not found';
        }else{
			$err=$name.' not found';
        }
        return $this->getItem($name, $index-$offset, $err);
	}
    /**
     * Get an equiped item by name
     * @param string $name
     * @param int $index 
     * @return Item
     */
    public function getEquipedItemByName($name,$index){
        $offset=0;
        foreach(self::$slots as $slot){
            if($this->isItemNameOnSlot($name, $slot)){
                $offset++;
                if($index-$offset==0){
                   //found!
                    return $this->getItemsBySlot($slot);
                    break;
                }
            }
        }
        MessageSystem::addErrorMessage('Item not found');
        return null;
    }
    /**
     * This function receive an item name and index and return an item visible to the character
     * This item may be on character hands or on the floor
     * @param string $name Element name
     * @param int $index Element number
     * @return Item|string
     */
    public function getItem($name,$index,$errorMessage=null){
     	$offset=0;
        $room=$this->getRoom();
        //the item may be on right hand
        if($this->isItemNameOnSlot($name, AMUD_SLOT_RIGHT_HAND)){
            $offset++;
            if($index-$offset==0){
                //found!
                return $this->getItemsBySlot(AMUD_SLOT_RIGHT_HAND);
            }
        }
		//the item may be on left hand
		//Get the left hand item
        if($this->isItemNameOnSlot($name, AMUD_SLOT_LEFT_HAND)){
            $offset++;
            if($index-$offset==0){
                //found!
                return $this->getItemsBySlot(AMUD_SLOT_LEFT_HAND);
            }
		}

		//the item may be on floor
		$floor=$room->getItemsByName($name,$index-$offset);

		if($offset+count($floor)>=$index){
			//found!
			$floor=array_slice($floor,$index-1-$offset,1,TRUE);
			$el = new Item(key($floor));
			return $el;
		}else{
            if($errorMessage===NULL){
                if($index>1){
                    $errorMessage=$name.'['.$index.'] not found';
                }else{
                    $errorMessage=$name.' not found';
                }
            }
            MessageSystem::addErrorMessage($errorMessage);
            return null;
        }

    }
    /**
     * Verify if a slot have an item with $name
     * @param string $name
     * @param string $slot
     * @return bool 
     */
    public function isItemNameOnSlot($name,$slot){
        //Get the slot item
		$item=$this->getItemsBySlot($slot);
		return ($item!=null && Utils::startWith($item->getType(),$name));
    }
    /**
     * Equip an item
     * @param Item $item
     * @param Item $container
     * @return string
     */
    public function equip($item,$container){
        $slots=$item->getEquipableSlots();
        foreach($slots as $slot){
            if($this->items[$slot]->getId()==null){
                //found a free slot!
                if($item->removeFrom($this, $container)){
                    $this->items[$slot]=$item;
                    $this->saveValues();
                    $this->getRoom()->broadcastMessage($this->charId.' equiped '.$item->getType(), $this);
                    return 'You equiped '.$item->getType();
                }else{
                    return null;
                }
                break;
            }
        }
        MessageSystem::addErrorMessage('Item not equipable or no free slots to equip it.');
        return null;
    }
    /**
     * Remove an item
     * @param Item $item
     * @return string
     */
    public function remove($item){
        $slot=$this->getFreeHandSlot();
        if($slot===null){
            MessageSystem::addErrorMessage('Your hands are full');
            return null;
        }
        if($this->removeItem($item)){
            $this->items[$slot]=$item;
            $this->saveValues();
            $this->getRoom()->broadcastMessage($this->charId.' removed '.$item->getType(), $this);
            MessageSystem::addCharacterMessage('You removed '.$item->getType());
        }else{
            MessageSystem::addErrorMessage('Item not equiped');
            return null;
        }
    }
    /**
     * Take an item to your hands
     * @param Item $item
     * @param int $num
     * @param Item $container
     * @return string
     */
    public function takeItem($item,$num,$container){
        if(!$item->stackHaveThatMuch($num)){
            return null;
        }
        $handSlot=$this->getFreeHandSlot($item->getType());
        if($handSlot==null){
            MessageSystem::addErrorMessage('Your hands are full');
            return null;
        }else{
            $oldStack=$item->stack;
            if($this->moveItemToSlot($item,$num,$handSlot,$container)){
                if($num=='all'){
                    $stack=$oldStack;
                }else{
                    $stack=$num;
                }
                $this->getRoom()->broadcastMessage($this->charId.' got '.$item->getType().'('.$stack.')', $this);
                return 'You got '.$item->getType().'('.$stack.')';
            }
        }

    }
    /**
     * Move an item to a slot
     * @param Item $item
     * @param int $num
     * @param string $slot
     * @param Item $container
     * @return string
     */
    public function moveItemToSlot($item,$num,$slot,$container){
        //This is a new item stack?
        $newItem=$item->split($num);
        if($newItem===null){
            //The char want to get all items on stack
            //so we must remove the item from original container
            if($container instanceof  Item){
                $flag=$container->container->removeItem($item,$num);
            }else{
                $flag=$this->getRoom()->removeItem($item,$num);
            }
        }else{
            //It is a new stack of items
            $item=$newItem;
            $flag=true;
        }
        
        if($flag){
            if($this->items[$slot]->getId()!=null){
                $this->items[$slot]->merge($item);
            }else{
                $this->items[$slot]=$item;
                $this->saveValues();
            }
            return true;
        }else{
            MessageSystem::addErrorMessage('Cannot remove item from container');
            return false;
        }
    }
    /**
     * Put an item in a container
     * @param Item $item
     * @param int $num
     * @param Item $container
     * @return string
     */
    public function putItemOnContainer($item,$num,$container){
        if(!$item->stackHaveThatMuch($num)){
            return null;
        }
        $newItem=$item->split($num);
        if($newItem!==null){
            if($container->container->addItem($newItem)){
                $this->getRoom()->broadcastMessage($this->charId.' put '.$newItem->getType().'('.$newItem->stack.')'.' in '.$container->getType(), $this);
                return 'You put '.$newItem->getType().'('.$newItem->stack.')'.' in '.$container->getType();
            }
        }else{
            if($container->container->addItem($item)){
                if($this->getItemsBySlot(AMUD_SLOT_RIGHT_HAND)->getId()==$item->getId()){
                    $this->removeItemBySlot(AMUD_SLOT_RIGHT_HAND);
                }elseif($this->getItemsBySlot(AMUD_SLOT_LEFT_HAND)->getId()==$item->getId()){
                    $this->removeItemBySlot(AMUD_SLOT_LEFT_HAND);
                }else{
                    $this->getRoom()->removeItem($item);
                }
                $this->getRoom()->broadcastMessage($this->charId.' put '.$item->getType().'('.$item->stack.')'.' in '.$container->getType(), $this);
                return 'You put '.$item->getType().'('.$item->stack.')'.' in '.$container->getType();
            }
        }
    }
    /**
     *This function keeps the database methods encapsulated and set the refresh with $value
     * @param bool $value new refresh value
     * @return bool
     */
    public function removeItem($item){
        foreach($this->items as $key=>$value){
            if($value->getId()==$item->getId()){
                $this->removeItemBySlot($key);
                return true;
                break;
            }
        }
        return false;
    }
    /**
     * Hide content of items, for look commands
     */
    public function hideItemsContent(){
        foreach($this->items as $item){
            $item->hideContent();
        }
    }
    /**
     * Remove an item from a slot
     * @param string $slot
     */
	protected function setRefresh($value){
		return $this->changeRefresh($value);
	}
    /**
     * Remove an item
     * @param Item $item
     * @return bool
     */
    private function removeItemBySlot($slot){
        $this->items[$slot] = new Item();
        $this->saveValues();
    }
    /**
     * Apply status on character capabilities
     * @return void
     */
	private function applyStatus(){
		//apply the character status
		$this->applyVetStatus($this->status);
		//apply the character items status
		foreach($this->items as $item){
			if($item!==null){
				$this->applyVetStatus($item->status);
			}
		}
	}
    /**
     * Apply an array of status
     * @param array $vet array of status
     * @return void
     */
	private function applyVetStatus($vet){
		foreach($vet as $status){
			foreach($status->capabilities as $cap=>$val){
				if(!isset($this->statusCapabilities[$cap])){
					$this->statusCapabilities[$cap]=$val*$status->level;
				}else{
					$this->statusCapabilities[$cap]+=$val*$status->level;
				}
			}
            foreach($status->commands as $command=>$val){
                if(!isset($this->statusCommands[$command])){
					$this->statusCommands[$command]=$val;
				}else{
					$this->statusCommands[$command]+=$val;
				}
            }
		}
	}
    /**
     * For $tickNum ticks update the character values
     * @param int $tickNum Number of ticks
     * @return void
     */
	private function updateValues($tickNum){
		//for each tick, calc values from statuses and set values
		$current=$this->updated;

		for($i=0;$i<$tickNum;$i++){
			$current+=$this->tickSize;
			$values=$this->getValuesAtTime($current);

			//echo "\nTotal ".'l:'.$values['life'].' v:'.$values['vitality'].' m:'.$values['mana'].' i:'.$values['intelligence']."\n";
			
			$this->HP+=ceil($values['life']+($this->baseVitality*$this->level*0.1));
			$this->MP+=ceil($values['mana']+($this->baseIntelligence*$this->level*0.1));	

			$tempLifeMax=($this->baseVitality+$values['vitality'])*$this->level*10;
			if($this->HP>$tempLifeMax){
				$this->HP=$tempLifeMax;
			}		

			$tempManaMax=($this->baseIntelligence+$values['intelligence'])*$this->level*10;
			if($this->MP>$tempManaMax){
				$this->MP=$tempManaMax;
			}		
			if($this->HP<=0){
				$this->addStatus('dead',$current,1);
				break;
			}
		}
		//remove ALL outdated status for this char and his items
		$this->removeOutdatedStatus();
	
	}
    /**
     * Construct a vector for life and mana for a timestamp
     * @param int $time
     * @return array
     */
	private function getValuesAtTime($time){
		$vector=array();
		$vector['life']=0;
		$vector['mana']=0;
		$vector['vitality']=0;
		$vector['intelligence']=0;
		
		$vector=$this->getValuesAtTimeFromStatusVector($this->status,$vector,$time);
		//apply the character items status
		foreach($this->items as $item){
			if($item!==null){
				$vector=$this->getValuesAtTimeFromStatusVector($item->status,$vector,$time);
			}
		}
		return $vector;
	}
	/**
     * Calc the values at each time from status vector
     * @param array $statuses array of status
     * @param array $vector array of capabilities
     * @param int $time Time
     * @return array
     */
	private function getValuesAtTimeFromStatusVector($statuses,$vector,$time){
		//The entry must be an array
		if(!is_array($statuses) || count($statuses)==0){
			return $vector;
		}
		foreach($statuses as $status){
			//echo "\nstatus:".$status->id;
			//identify the status that must be applied
			if($status->applied>$time){
				//this status will be applied in future
				continue;
			}
			if($status->applied+($status->duration*$this->tickSize)<$time && $status->applied!==null && $status->duration!==null){
				//this status don't have effect anymore
				continue;
			}
			if(isset($status->capabilities['life regen'])){
				$vector['life']+=$status->capabilities['life regen']*$status->level;
				//echo " lr:".$status->capabilities['life regen']*$status->level;
			}
			if(isset($status->capabilities['mana regen'])){
				$vector['mana']+=$status->capabilities['mana regen']*$status->level;
				//echo " mr:".$status->capabilities['mana regen']*$status->level;
			}
			if(isset($status->capabilities['vitality'])){
				$vector['life']+=$status->capabilities['vitality']*$this->level*0.1*$status->level;
				$vector['vitality']+=$status->capabilities['vitality']*$status->level;
				//echo " lr:".$status->capabilities['vitality']*$this->level*0.1*$status->level;
				//echo " v:".$status->capabilities['vitality']*$status->level;
			}
			if(isset($status->capabilities['intelligence'])){
				$vector['mana']+=$status->capabilities['intelligence']*$this->level*0.1*$status->level;
				$vector['intelligence']+=$status->capabilities['intelligence']*$status->level;
				//echo " mr:".$status->capabilities['intelligence']*$this->level*0.1*$status->level;
				//echo " i:".$status->capabilities['intelligence']*$status->level;
			}
		}
		return $vector;
	}
    /**
     *Calculate the capabilities
     * @return void
     */
	private function calcCapabilities(){
	
		//attributes are equal to base + status granted attributes
		//Strength
		$this->capabilities['strength']=(isset($this->statusCapabilities['strength']) && $this->statusCapabilities['strength']!=null)?
										$this->statusCapabilities['strength']+$this->baseStrength			:	$this->baseStrength;
		//Intelligence
		$this->capabilities['intelligence']=(isset($this->statusCapabilities['intelligence']) && $this->statusCapabilities['intelligence']!=null)?
										$this->statusCapabilities['intelligence']+$this->baseIntelligence	:	$this->baseIntelligence;
		//Dexterity
		$this->capabilities['dexterity']=(isset($this->statusCapabilities['dexterity']) && $this->statusCapabilities['dexterity']!=null)?
										$this->statusCapabilities['dexterity']+$this->baseDexterity			:	$this->baseDexterity;
		//Vitality
		$this->capabilities['vitality']=(isset($this->statusCapabilities['vitality']) && $this->statusCapabilities['vitality']!=null)?
										$this->statusCapabilities['vitality']+$this->baseVitality			:	$this->baseVitality;

		//Capabilities
		//Documentation http://code.google.com/p/amud/wiki/Players#Capabilities

		//Phys. Def. = Σ( status + equipment ) + ( strength X Σ( player levels) X 0.1 )
		$cap='physical defence';
		$this->capabilities[$cap]=$this->capabilities['strength']*$this->level*0.1;
		$this->addStatusValue($cap);
	
		//Phys. Attack = Σ( status + equipment ) + ( strength X Σ( player levels) )
		$cap='physical attack';
		$this->capabilities[$cap]=$this->capabilities['strength']*$this->level;
		$this->addStatusValue($cap);

		//current weight is equal to items weight
		$cap='weight';
		$this->calcWeight();
		$this->addStatusValue($cap);
		
		//Maximum Weight = Σ( status + equipment ) + ( strength X Σ( player levels) )
		$cap='weight max';
		$this->capabilities[$cap]=$this->capabilities['strength']*$this->level;
		$this->addStatusValue($cap);
		if($this->capabilities['weight']>$this->capabilities['weight max'] && !$this->haveStatus('overloaded')){
			$this->addStatus('overloaded',$this->tick,1);
		}elseif($this->capabilities['weight']<=$this->capabilities['weight max'] && $this->haveStatus('overloaded')){
			$this->disableStatus('overloaded');
		}
		
		
		$freeWeight=$this->capabilities['weight']/$this->capabilities['weight max'];
		
		//Accuracy = Σ( status + equipment ) + ( dexterity X Σ( player levels) X ( percentage of unload weight ) )
		$cap='accuracy';
		$this->capabilities[$cap]=$this->capabilities['dexterity']*$this->level*$freeWeight;
		$this->addStatusValue($cap);
		
		//Dodge = Σ( status + equipment ) + ( dexterity X Σ( player levels) X ( percentage of unload weight ) )
		$cap='dodge';
		$this->capabilities[$cap]=$this->capabilities['dexterity']*$this->level*$freeWeight;
		$this->addStatusValue($cap);

		//Velocity = Σ( status + equipment ) + intelligence + ( dexterity X Σ( player levels) X ( percentage of unload weight ) )
		$cap='velocity';
		$this->capabilities[$cap]=$this->capabilities['intelligence']+($this->capabilities['dexterity']*$this->level*$freeWeight);
		$this->addStatusValue($cap);

		//Critical % = Σ( status + equipment ) + ( ( intelligence + dexterity ) X 0.2 )
		$cap='critical';
		$this->capabilities[$cap]=($this->capabilities['intelligence']+$this->capabilities['dexterity'])*0.2;
		$this->addStatusValue($cap);
		
		//Life = Σ( status + equipment ) + ( vitality X Σ( player levels) X 10 )
		$cap='life max';
		$this->capabilities[$cap]=$this->capabilities['vitality']*$this->level*10;
		$this->addStatusValue($cap);
		if($this->HP>$this->capabilities['life max']){
			$this->HP=$this->capabilities['life max'];
		}

		//Life Regeneration = Σ( status + equipment ) + ( vitality X Σ( player levels) X 0.1 )
		$cap='life regen';
		$this->capabilities[$cap]=$this->capabilities['vitality']*$this->level*0.1;
		$this->addStatusValue($cap);

		//Mana = Σ( status + equipment ) + ( intelligence X Σ( player levels) X 10 )
		$cap='mana max';
		$this->capabilities[$cap]=$this->capabilities['intelligence']*$this->level*10;
		$this->addStatusValue($cap);
		if($this->MP>$this->capabilities['mana max']){
			$this->MP=$this->capabilities['mana max'];
		}
		
		//Mana Regeneration = Σ( status + equipment ) + ( intelligence X Σ( player levels) X 0.1 )
		$cap='mana regen';
		$this->capabilities[$cap]=$this->capabilities['intelligence']*$this->level*0.1;
		$this->addStatusValue($cap);

		//Magical Attack = Σ( status + equipment ) + ( intelligence X Σ( player levels) )
		$cap='magical attack';
		$this->capabilities[$cap]=$this->capabilities['intelligence']*$this->level;
		$this->addStatusValue($cap);

		//Magical Defence = Σ( status + equipment ) + ( intelligence X Σ( player levels) X 0.1 )
		$cap='magical defence';
		$this->capabilities[$cap]=$this->capabilities['intelligence']*$this->level*0.1;
		$this->addStatusValue($cap);
		
	}
    /**
     *Add a value to a capability
     * @param string $cap capability
     * @return void
     */
	private function addStatusValue($cap){
		if(isset($this->statusCapabilities[$cap]) && $this->statusCapabilities[$cap]!=null){
			$this->capabilities[$cap]+=$this->statusCapabilities[$cap];
		}
	}
    /**
     * Calculate the weight and set it on capability
     * @return void
     */
	private function calcWeight(){
		$this->capabilities['weight']=0;
		foreach($this->items as $item){
			if($item!==null){
				$this->capabilities['weight']+=$item->weight;
			}
		}
	}
    /**
     * Verify the existence of a status
     * @param string $value Status name
     * @return bool
     */
	private function haveStatus($value){
		foreach($this->status as $status){
			if($status->id==$value)
				return true;
		}
		return false;
	}
    //***********************************************************************************************************
    //					DATABASE METHODS
    //***********************************************************************************************************
	
	/**
     * Retrieve the current values and set on object
     * @return void
     */
    private function retrieveCurrValues(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'SELECT `life`,`mana`,`experience`,`room`,UNIX_TIMESTAMP(`updated`),`strength`,`dexterity`,`vitality`,`intelligence`,'.
								implode(',',self::$slots).
								' FROM `char`  '.
								'WHERE char_id = ? '.
								'LIMIT 1'
								);
	    $stmt->bind_param("s", $this->charId);
	    $stmt->bind_result(	$this->HP,$this->MP,$this->XP,$this->room,$this->updated,
	    					$this->baseStrength,$this->baseDexterity,$this->baseVitality,$this->baseIntelligence,
	    					$this->items[AMUD_SLOT_LEFT_RING],	$this->items[AMUD_SLOT_RIGHT_RING],
	    					$this->items[AMUD_SLOT_LEFT_HAND],	$this->items[AMUD_SLOT_RIGHT_HAND],
	    					$this->items[AMUD_SLOT_LEFT_EAR],	$this->items[AMUD_SLOT_RIGHT_EAR],
	    					$this->items[AMUD_SLOT_NECK],		$this->items[AMUD_SLOT_HEAD],
	    					$this->items[AMUD_SLOT_FACE],		$this->items[AMUD_SLOT_FEET],
	    					$this->items[AMUD_SLOT_LEGS],		$this->items[AMUD_SLOT_CHEST],
	    					$this->items[AMUD_SLOT_HANDS],		$this->items[AMUD_SLOT_ARMS],
	    					$this->items[AMUD_SLOT_BACK],		$this->items[AMUD_SLOT_WAIST] );
	    $stmt->execute();
	    $stmt->fetch();
  		$stmt->close(); 
  		foreach($this->items as $slot=>$itemId){
			$this->items[$slot]=new Item($itemId);
  		}
	}
    /**
     * Retrieve current status
     * @return void
     */
	private function retrieveCurrStatus(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'SELECT  `status`, '.
										'`level`, '.
										'UNIX_TIMESTAMP(`applied`) '.
								'FROM `character_status` '.		
								'WHERE   `char_id` = ? '
								);
	    $stmt->bind_param("s", $this->charId);
	    $stmt->bind_result($status,$level,$apply);
	    $stmt->execute();
	    while($stmt->fetch()){
	    	$this->status[]=new Status($status,$level,$apply);
	    	if($status=='dead')
	    		$this->dead=true;
	    }
  		$stmt->close();
	}
	/**
     * Retrieve current roles
     * @return void
     */
	private function retrieveRoles(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'SELECT  `role`, '.
										'`level` '.
								'FROM `characters_roles` '.		
								'WHERE   `char_id` = ? '
								);
	    $stmt->bind_param("s", $this->charId);
	    $stmt->bind_result($role,$level);
	    $stmt->execute();
	    $this->level=0;
	    while($stmt->fetch()){
	    	$this->roles[$role]=$level;
	    	$this->level+=$level;
	    }
  		$stmt->close();
	}
    /**
     * Retrieve current room
     * @return void
     */
	private function retrieveRoom(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'SELECT `room` '.
								'FROM `char`  '.
								'WHERE char_id = ? '.
								'LIMIT 1'
								);
	    $stmt->bind_param("s", $this->charId);
	    $stmt->bind_result(	$this->room);
	    $stmt->execute();
	    $stmt->fetch();
  		$stmt->close(); 
	}
    /**
     * Remove useless status
     * @return void
     */
	private function removeUselessStatus(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'DELETE FROM character_status '.
								'WHERE EXISTS ( '.
								'	SELECT * '.
								'	FROM `status` AS s '.
								'	WHERE	character_status.`status` = s.`status` AND '.
								'			(floor( ( ? - UNIX_TIMESTAMP( `applied` ) ) /? ) > duration) AND '.
								'			(s.status NOT IN ( '.
								'									SELECT c.status '.
								'									FROM status_capabilities c '.
								'									WHERE	 s.status = c.status	AND '.
								'											(capability = \'life regen\'	OR '.
								'											 capability = \'vitality\'	OR '.
								'											 capability = \'mana regen\'	OR '.
								'											 capability = \'intelligence\' '.
								'											 ) '.
								'									) '.
								'			) '.
								'	)	'
								);
	    $stmt->bind_param("ii", $this->tick, $this->tickSize);
	    $stmt->execute();
  		$stmt->close();
	}
    /**
     *Remove a status
     * @param string $value status name
     */
	private function removeStatus($value){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'DELETE FROM character_status '.
								'WHERE `status` = ? AND char_id = ?'
								);
	    $stmt->bind_param("ss", $value, $this->charId);
	    $stmt->execute();
  		$stmt->close();
		foreach($this->status as $key=>$status){
			if($status->id==$value){
				unset($this->status[$key]);
			}
		}
	}

    /**
     * Set a status on time $time with the level $level
     * @param string $status The new status name
     * @param int $time The time of set
     * @param int $level The level of this status
     * @return void
     */
	private function setStatus($status,$time,$level){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare('INSERT INTO `character_status` (`char_id` ,`status` ,`applied` ,`level`) VALUES (?,?,FROM_UNIXTIME(?),?)');
	    $stmt->bind_param("ssii", $this->charId, $status,$time,$level);
	    $stmt->execute();
  		$stmt->close();
  		$this->status[]=new Status($status,$level,$time);
	}

    /**
     * Save the values on DB
     * @return void
     */
	private function saveValues(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'UPDATE `char` SET `life`=?,`mana`=?,`experience`=?,`room`=?,'.
													'`updated`=FROM_UNIXTIME(?),'.
													'`strength`=?,`dexterity`=?,`vitality`=?,`intelligence`=?, '.
													implode(' = ?, ',self::$slots).' = ?  '.
								'WHERE char_id = ? '.
								'LIMIT 1'
								);
	    $stmt->bind_param(	'iiiiiiiiiiiiiiiiiiiiiiiiis',
	    					$this->HP,$this->MP,$this->XP,$this->room,$this->updated,
	    					$this->baseStrength,$this->baseDexterity,$this->baseVitality,$this->baseIntelligence,
	    					$this->items[AMUD_SLOT_LEFT_RING]->getId(),	$this->items[AMUD_SLOT_RIGHT_RING]->getId(),
	    					$this->items[AMUD_SLOT_LEFT_HAND]->getId(),	$this->items[AMUD_SLOT_RIGHT_HAND]->getId(),
	    					$this->items[AMUD_SLOT_LEFT_EAR]->getId(),	$this->items[AMUD_SLOT_RIGHT_EAR]->getId(),
	    					$this->items[AMUD_SLOT_NECK]->getId(),		$this->items[AMUD_SLOT_HEAD]->getId(),
	    					$this->items[AMUD_SLOT_FACE]->getId(),		$this->items[AMUD_SLOT_FEET]->getId(),
	    					$this->items[AMUD_SLOT_LEGS]->getId(),		$this->items[AMUD_SLOT_CHEST]->getId(),
	    					$this->items[AMUD_SLOT_HANDS]->getId(),		$this->items[AMUD_SLOT_ARMS]->getId(),
	    					$this->items[AMUD_SLOT_BACK]->getId(),		$this->items[AMUD_SLOT_WAIST]->getId(),
	    					$this->charId );
	    $stmt->execute();
	    $stmt->fetch();
  		$stmt->close(); 

	}

    /**
     * Save the room on DB
     * @return void
     */
	private function saveRoom(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'UPDATE `char` SET 	`room`=?,'.
													'`updated`=FROM_UNIXTIME(?) '.
								'WHERE char_id = ? '.
								'LIMIT 1'
								);
	    $stmt->bind_param('iis',$this->room,$this->updated,$this->charId);
	    $stmt->execute();
	    $stmt->fetch();
  		$stmt->close(); 

	}

    /**
     * Remove outdated status
     * @return void
     */
	private function removeOutdatedStatus(){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'DELETE FROM `items_status` '.
								'WHERE  '.
								'	EXISTS( '.
								'		SELECT *  '.
								'		FROM `char`  '.
								'		WHERE 	char_id=? AND '.
								'			(	`items_status`.item='.
												implode(' OR `items_status`.item=',self::$slots).
								'			) '.
								'	) '.
								'	AND			 '.
								'	EXISTS ( '.
								'		SELECT * '.
								'		FROM `status` AS s '.
								'		WHERE	`items_status`.`status` = s.`status` AND '.
								'				(UNIX_TIMESTAMP( `applied` ) + duration*?) <= ? '.
								'	)'
								);
						
	    $stmt->bind_param("sii", $this->charId, $this->tickSize, $this->tick);
   	    $stmt->execute();
  		$stmt->close();
		$stmt = $db->prepare(	'DELETE FROM `character_status` '.
								'WHERE '.
								'	EXISTS ( '.
								'		SELECT * '.
								'		FROM `status` AS s '.
								'		WHERE	character_status.`status` = s.`status` AND '.
								'				char_id=? AND '.
								'				(UNIX_TIMESTAMP( `applied` ) + duration*?) <= ?'.
								'	)'
								);
						
	    $stmt->bind_param("sii", $this->charId, $this->tickSize, $this->tick);
	    $stmt->execute();
  		$stmt->close();
	}
	/**
     * Change the refresh value
     * @param bool $value the new refresh value
     * @return bool
     */
	private function changeRefresh($value){
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'UPDATE `char`  '.
								'SET refresh = ? '.
								'WHERE `char_id` = ? '
								);
	    $stmt->bind_param("is", $value,$this->charId);

	    $stmt->execute();
   		$refresh=$stmt->affected_rows;
		$stmt->close(); 
		return $refresh;
	}
}